/**
 * Author: luoqi
 * Created Date: 2025-10-15 21:16:10
 * Last Modified: 2025-11-24 22:55:32
 * Modified By: luoqi at <**@****>
 * Copyright (c) 2025 <*****>
 * Description:
 */

#include <utils/log.hpp>
#include <errno.h>
#include <stdio.h> 
#include <string.h>
#include <poll.h>
#include "canport.h"

CanPort::CanPort(const char *ifname, int bitrate, CanType type, int dbitrate)
{
    this->ifname_ = ifname;
    this->bitrate_ = bitrate;
    this->dbitrate_ = dbitrate;
    this->type_ = type;
    this->sock_ = -1;
    char cmd[128];
    snprintf(cmd, sizeof(cmd), "ip link set down %s", ifname);
    int ret = system(cmd);
    if(ret != 0) {
        perror(cmd);
        goto err_construct;
    }
    if(type == CAN) {
        snprintf(cmd, sizeof(cmd), "ip link set %s type can bitrate %d", ifname, bitrate);
    } else {
        snprintf(cmd, sizeof(cmd), "ip link set %s type can bitrate %d dbitrate %d fd on", ifname, bitrate, dbitrate);
    }
    ret = system(cmd);
    if(ret != 0) {
        perror(cmd);
        goto err_construct;
    }
    snprintf(cmd, sizeof(cmd), "ip link set up %s", ifname);
    ret = system(cmd);
    if(ret != 0) {
        perror(cmd);
        goto err_construct;
    }

    return;
err_construct:
    printf(" canport must using \"sudo\" to run\r\n");
    close();
    return;
}

CanPort::~CanPort()
{
    close();
}

int CanPort::open()
{
    int ret = 0;
    if((sock_ = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
        perror("#! can socket create failed");
        goto err_open;
    }

    // int flags = fcntl(sock_, F_GETFL, 0);
    // fcntl(sock_, F_SETFL, flags | O_NONBLOCK);

    // can_err_mask_t err_mask = CAN_ERR_MASK;
    // setsockopt(sock_, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &err_mask, sizeof(err_mask));

    strcpy(ifr_.ifr_name, ifname_);
    ret = ioctl(sock_, SIOCGIFINDEX, &ifr_);
    if(ret < 0) {
        perror("#! can socket set index failed");
        goto err_open;
    }
    addr_.can_family = AF_CAN;
    addr_.can_ifindex = ifr_.ifr_ifindex;

    if(type_ == CANFD) {
        int enable_canfd = 1;
        if(setsockopt(sock_, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &enable_canfd, sizeof(enable_canfd)) < 0) {
            perror("setsockopt CAN_RAW_FD_FRAMES");
            goto err_open;
        }
    }

    ret = bind(sock_, (struct sockaddr *)&addr_, sizeof(addr_));
    if(ret < 0) {
        perror("#! can socket bind failed");
        goto err_open;
    }
    QLog::info("can port {} opened, type: {}, bitrate: {}, dbitrate: {}", ifr_.ifr_name, type_ == CAN ? "CAN" : "CANFD", bitrate_, dbitrate_);
    return 0;
err_open:
    QLog::err(" canport open failed, ifname: {}", ifname_);
    close();
    return -1;
}

int CanPort::close()
{
    if(sock_ >= 0) {
        ::close(sock_);
        sock_ = -1;
    }
    return 0;
}

int CanPort::send(uint16_t id, void *data, uint8_t sz)
{
    int ret = 0;
    if(type_ == CAN) {
        if(sz > 8) {
            QLog::err("CAN send error: data size {} exceeds 8 bytes", sz);
            return -1;
        };
        CanFrame frame;
        frame.can_id = id;
        frame.len = sz;
        memcpy(frame.data, data, sz);
        ret = write(sock_, &frame, sizeof(frame));

    } else {
        if(sz > 64) {
            QLog::err("CANFD send error: data size {} exceeds 64 bytes", sz);
            return -1;
        };
        CanfdFrame frame;
        frame.can_id = id;
        frame.len = sz;
        memcpy(frame.data, data, sz);
        ret = write(sock_, &frame, sizeof(frame));
    }
    if(ret < 0) {
        QLog::err("CAN send error: {} (errno={})", strerror(errno), errno);
    }
    return ret;
}

int CanPort::recv(uint16_t &id, void *rbuf, uint8_t &sz, int timeout_ms)
{
    struct pollfd pfd;
    pfd.fd = sock_;
    pfd.events = POLLIN;
    pfd.revents = 0;

    int ret = poll(&pfd, 1, timeout_ms);
    if(ret < 0) {
        perror("poll failed");
        return -1;
    } else if(ret == 0) {
        errno = ETIMEDOUT;
        return 0;
    }

    if(type_ == CAN) {
        CanFrame frame;
        int nbytes = read(sock_, &frame, sizeof(frame));
        if(nbytes < 0) {
            return -1;
        }
        id = frame.can_id;
        sz = frame.len;
        memcpy(rbuf, frame.data, sz);
        return nbytes;
    } else {
        CanfdFrame frame;
        int nbytes = read(sock_, &frame, sizeof(frame));
        if(nbytes < 0) {
            return -1;
        }
        id = frame.can_id;
        sz = frame.len;
        memcpy(rbuf, frame.data, sz);
        return nbytes;
    }
}

int CanPort::send(CanFrame frame)
{
    return write(sock_, &frame, sizeof(frame));
}

int CanPort::send(CanfdFrame frame)
{
    return write(sock_, &frame, sizeof(frame));
}

int CanPort::recv(CanFrame &frame, int timeout_ms)
{
    struct pollfd pfd;
    pfd.fd = sock_;
    pfd.events = POLLIN;
    pfd.revents = 0;

    int ret = poll(&pfd, 1, timeout_ms);
    if(ret < 0) {
        perror("poll failed");
        return -1;
    } else if(ret == 0) {
        errno = ETIMEDOUT;
        return 0;
    }

    int nbytes = read(sock_, &frame, sizeof(frame));
    if(nbytes < 0) {
        return -1;
    }
    return nbytes;
}

int CanPort::recv(CanfdFrame &frame, int timeout_ms)
{
    struct pollfd pfd;
    pfd.fd = sock_;
    pfd.events = POLLIN;
    pfd.revents = 0;

    int ret = poll(&pfd, 1, timeout_ms);
    if(ret < 0) {
        perror("poll failed");
        return -1;
    } else if(ret == 0) {
        errno = ETIMEDOUT;
        return 0;
    }

    int nbytes = read(sock_, &frame, sizeof(frame));
    if(nbytes < 0) {
        return -1;
    }
    return nbytes;
}
